/* -------------------------------------------------------------------
 *    Application class for RubiksCube
 *
 *    $File: RubiksCubeView.cpp
 *    $Date: 2013/01/31 $
 *    $Revision: #1 $
 * -------------------------------------------------------------------*/
#include "RubiksCubeView.h"

RubiksCubeView::RubiksCubeView(RubiksCubeModel &m) : model(m), pointLight(new vlgGLPointLight(GL_LIGHT0)), explosionGrade(m.getExplosionGrade()) {
	model.attach(this);
}

void RubiksCubeView::initContext(void) {
	leftMirror = new vlgSolidPlate();
	leftMirror->setColor(0.7f, 0.6f, 0.6f, 0.7f);
	bottomMirror = new vlgSolidPlate();
	bottomMirror->setColor(0.6f, 0.7f, 0.6f, 0.7f);
	backMirror = new vlgSolidPlate();
	backMirror->setColor(0.6f, 0.6f, 0.7f, 0.7f);
	float lightPosition[3]={15.0f, 15.0f, 15.0f};
	pointLight->init();
	pointLight->setPosition(lightPosition);
	pointLight->on();
	// 0 in den Stencil Buffer schreiben bei clear
	glClearStencil(0);
}

void RubiksCubeView::draw(void) {

	// 1. render back mirror in stencil buffer
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_STENCIL_TEST);
	    // stecil test fails, but values inc by 1
		glStencilFunc(GL_NEVER, 0, 0x0);
		glStencilOp(GL_INCR, GL_INCR, GL_INCR);
		drawBackMirror();
	glEnable(GL_DEPTH_TEST);
	// only if stencil value equals 1
	glStencilFunc(GL_EQUAL, 1, 0x1);
	glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

	// 2. mirror cube on back
	glPushMatrix();
		glTranslatef(0.0f, 0.0f, -2 * distance);
		glScalef(1.0f, 1.0f, -1.0f);
		pointLight->setGLPosition();
		glCullFace(GL_FRONT);
			glEnable(GL_NORMALIZE);
			   drawRubiksCube();
			glDisable(GL_NORMALIZE);
		glCullFace(GL_BACK);
	glPopMatrix();
	
	// 3. reset stencil to 0
	glClear(GL_STENCIL_BUFFER_BIT);
	// 4. render left mirror in stencil buffer
	glDisable(GL_DEPTH_TEST);
	    // stecil test fails, but values inc by 1
		glStencilFunc(GL_NEVER, 0, 0x0);
		glStencilOp(GL_INCR, GL_INCR, GL_INCR);
		drawLeftMirror();
		glStencilFunc(GL_EQUAL, 1, 0x1);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	glEnable(GL_DEPTH_TEST);	
	// 5. render reflected cube in left wall
	glPushMatrix();
		glTranslatef(-2 * distance, 0.0f, 0.0f);
		glScalef(-1.0f, 1.0f, 1.0f);
		pointLight->setGLPosition();
		// swap back to front face
		glCullFace(GL_FRONT);
			glEnable(GL_NORMALIZE);
			   drawRubiksCube();
			glDisable(GL_NORMALIZE);
		glCullFace(GL_BACK);
	glPopMatrix();

	// 6. reset stencil
	glClear(GL_STENCIL_BUFFER_BIT);

	// 7. floor Spiegel in den Stencil Buffer rendern
	glDisable(GL_DEPTH_TEST);	
		glStencilFunc(GL_NEVER, 0, 0x0);
		glStencilOp(GL_INCR, GL_INCR, GL_INCR);
		drawBottomMirror();
		glStencilFunc(GL_EQUAL, 1, 0x1);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
	glEnable(GL_DEPTH_TEST);	
	
	// 8. render reflected cube in left mirror
	glPushMatrix();
		glTranslatef(0.0f, -2 * distance, 0.0f);
		glScalef(1.0f, -1.0f, 1.0f);
		pointLight->setGLPosition();
		glCullFace(GL_FRONT);
			glEnable(GL_NORMALIZE);
			   drawRubiksCube();
			glDisable(GL_NORMALIZE);
		glCullFace(GL_BACK);
	glPopMatrix();	
	// reset
	glDisable(GL_STENCIL_TEST);
	glEnable(GL_DEPTH_TEST);
        glFrontFace(GL_CCW);	
	pointLight->setGLPosition();
	
	// 9. blend bottom mirror
	glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		drawBackMirror();
		drawLeftMirror();
		drawBottomMirror();
	glDisable(GL_BLEND);

	// 10. draw the original
	drawRubiksCube();
}

bool RubiksCubeView::update(const vlgSubject &changedSubject) {
	if (&changedSubject == &model) {
		explosionGrade = model.getExplosionGrade();
		//
	}
	return true;
}

void RubiksCubeView::drawBottomMirror(void) {
	glDisable(GL_LIGHTING);
	glPushMatrix();
		glTranslatef(0.0f, -distance, 0.0f);
		glScalef(0.75f, 0.75f, 0.75f);
		bottomMirror->draw();
	glPopMatrix();
	glEnable(GL_LIGHTING);
}

void RubiksCubeView::drawBackMirror(void) {
	glDisable(GL_LIGHTING);
	glPushMatrix();
		glTranslatef(0.0f, 0.0f, -distance);
		glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
		glScalef(0.75f, 0.75f, 0.75f);
		backMirror->draw();
	glPopMatrix();
	glEnable(GL_LIGHTING);
}

void RubiksCubeView::drawLeftMirror(void) {
	glDisable(GL_LIGHTING);
	glPushMatrix();
		glTranslatef(-distance, 0.0f, 0.0f);
		glRotatef(-90.0f, 0.0f, 0.0f, 1.0f);
		glScalef(0.75f, 0.75f, 0.75f);
		leftMirror->draw();
	glPopMatrix();
	glEnable(GL_LIGHTING);
}

void RubiksCubeView::drawRubiksCube(void) 
{
	glMaterialf(GL_FRONT, GL_SHININESS, 90.0f);
	glPushMatrix();
		// move rubikscube into center
		glTranslatef(-1.0f, -1.0f, -1.0f);
		// display each cuby
		for (int x = 0; x < 3; ++x) {  // dim X
			for (int y = 0; y < 3; ++y) {  // dim Y
				for (int z = 0; z < 3; ++z) { // dim Z
					glPushMatrix();
						glTranslatef(explosionGrade * x, explosionGrade * y, explosionGrade * z);
						model.cubies[x][y][z].draw();
						glColor3f(0.0f, 0.0f, 0.0f);
						glLineWidth(5.0f);
						glutWireCube(1.0f);
					glPopMatrix();
				}
			}
		}
	glPopMatrix();
}